#include <map>
#include <vector>
using namespace std;
#include <boost/shared_ptr.hpp>
#include <wx/wx.h>
#include <wx/listctrl.h>
#include <wx/filename.h>
#include <wx/thread.h>
#include <wx/dir.h>
#include "RWLock.h"
#include "DirectoryWatcher.h"
#include "DirectoryCache.h"
#include "ItemInfo.h"
#include "Utils.h"

DirectoryCache::DirectoryCache() : handles(NULL), handlesCount(0), done(false)
{
}
void DirectoryCache::Initialize()
{
    handles = new HANDLE[1];
    handlesToRemove = new bool[1];
    handlesToRemove[0] = false;
    handlesCount = 1;
    handles[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
    conditionMutex = new wxMutex();
    threadCondition = new wxCondition(*conditionMutex);
    RWLock_Init(&readWriteLock);
    Create();
    Run();
    lastClean = GetTickCount();
}
wxThread::ExitCode DirectoryCache::Entry()
{
    while (!TestDestroy())
    {
        if (done)
            continue;
        conditionMutex->Lock();
        threadCondition->Broadcast();
        conditionMutex->Unlock();
        DWORD waitStatus = WaitForMultipleObjects(handlesCount, handles, FALSE, INFINITE);
        if ((waitStatus >= WAIT_OBJECT_0) && (waitStatus < WAIT_OBJECT_0 + handlesCount))
        {
            unsigned int handleId = waitStatus - WAIT_OBJECT_0;
            if (handleId > 0)
            {
                {
                    wxMutexLocker locker(*cacheEntriesByIndex[handleId]->accessGuard);
                    cacheEntriesByIndex[handleId]->changed = true;
                    FindNextChangeNotification(handles[handleId]);
                }
                for (unsigned int i = 0; i < watchers.size(); i++)
                    watchers[i]->DirectoryChanged(cacheEntriesByIndex[handleId]->path);
            }
        }
        CleanHandles2();
        DWORD currentTime = GetTickCount();
        if (currentTime - lastClean > 5 * 60 * 1000)
        {
            CleanHandles();
            lastClean = currentTime;
        }
    }
    for (unsigned int i = 1; i < handlesCount; i++)
        handlesToRemove[i] = true;
    CleanHandles2();
    CloseHandle(handles[0]);
    delete [] handles;
    delete [] handlesToRemove;
    delete conditionMutex;
    delete threadCondition;
    RWLock_Free(&readWriteLock);
    return 0;
}

void DirectoryCache::Signal(unsigned int handleId)
{
    SetEvent(handles[handleId]);
}
DirectoryCacheEntry *DirectoryCache::WatchDirectory(const wxString &path)
{
    map<wxString, DirectoryCacheEntry*>::iterator it = cacheEntriesByPath.find(path);
    if (it != cacheEntriesByPath.end())
        return it->second;
    DirectoryCacheEntry *entry = new DirectoryCacheEntry;
    entry->accessGuard = new wxMutex;
    entry->changed = true;
    entry->path = path;
    entry->useCount = 0;
    cacheEntriesByPath[path] = entry;
    HANDLE *newHandles = new HANDLE[handlesCount + 1];
    bool *newHandlesToRemove = new bool[handlesCount + 1];
    for (unsigned int i = 0; i < handlesCount; i++)
    {
        newHandles[i] = handles[i];
        newHandlesToRemove[i] = handlesToRemove[i];
    }
    newHandlesToRemove[handlesCount] = false;
    newHandles[handlesCount] = FindFirstChangeNotification(path, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_CREATION);
    cacheEntriesByIndex[handlesCount] = entry;
    handlesCount++;
    delete [] handles;
    handles = newHandles;
    delete [] handlesToRemove;
    handlesToRemove = newHandlesToRemove;
    return entry;
}
void DirectoryCache::UnwatchDirectory(const wxString &path)
{
    StartRead();
    map<wxString, DirectoryCacheEntry*>::iterator it = cacheEntriesByPath.find(path);
    if (it == cacheEntriesByPath.end())
    {
        EndRead();
        return;
    }
    DirectoryCacheEntry *entry = it->second;
    for (map<unsigned int, DirectoryCacheEntry*>::iterator it2 = cacheEntriesByIndex.begin(); it2 != cacheEntriesByIndex.end(); it2++)
    {
        if (it2->second == entry)
        {
            handlesToRemove[it2->first] = true;
        }
    }
    EndRead();
    conditionMutex->Lock();
    Signal(0);
    threadCondition->Wait();
    conditionMutex->Unlock();
}
void DirectoryCache::End()
{
    done = true;
    Signal(0);
}
bool DirectoryCache::GetDirectoryContent(const wxString &path, vector<ItemInfo *> &content, bool forceRefresh)
{
    bool needToSignal = false;
    DirectoryCacheEntry *entry;
    StartRead();
    map<wxString, DirectoryCacheEntry*>::iterator i = cacheEntriesByPath.find(path);
    if (i == cacheEntriesByPath.end())
    {
        UpgradeToWriter();
        entry = WatchDirectory(path);
        DowngradeToReader();
        needToSignal = true;
    }
    else
        entry = i->second;
    wxMutexLocker locker(*entry->accessGuard);
    if (entry->changed || forceRefresh)
    {
        if (GetDirectoryList(path, content))
        {
            entry->content = content;
            entry->changed = false;
        }
        else
        {
            EndRead();
            UnwatchDirectory(path);
            return false;
        }
    }
    else
        content = entry->content;

    entry->useCount++;
    EndRead();
    if (needToSignal)
        Signal(0);
    return true;
}
void DirectoryCache::RegisterWatcher(DirectoryWatcher *watcher)
{
    watchers.push_back(watcher);
}
void DirectoryCache::CleanHandles()
{
    StartRead();
    vector<bool> handlesToKeep(handlesCount);
    unsigned int newCount = 0;
    for (unsigned int i = 1; i < handlesCount; i++)
    {
        DirectoryCacheEntry *entry = cacheEntriesByIndex[i];
        if (!wxFileName::DirExists(entry->path) && entry->useCount == 0)
        {
            handlesToKeep[i] = false;
        }
        else
        {
            handlesToKeep[i] = true;
            newCount++;
        }
    }
    if (newCount == handlesCount - 1)
    {
        EndRead();
        return;
    }
    UpgradeToWriter();
    HANDLE *newHandles = new HANDLE[newCount + 1];
    newHandles[0] = handles[0];
    unsigned int j = 1;
    for (unsigned int i = 1; i < handlesCount; i++)
    {
        if (handlesToKeep[i])
        {
            newHandles[j] = handles[i];
            cacheEntriesByIndex[j] = cacheEntriesByIndex[i];
            j++;
        }
        else
        {
            cacheEntriesByPath.erase(cacheEntriesByIndex[i]->path);
            delete cacheEntriesByIndex[i]->accessGuard;
            for (vector<ItemInfo*>::iterator k = cacheEntriesByIndex[i]->content.begin(); k != cacheEntriesByIndex[i]->content.end(); k++)
                delete *k;
            delete cacheEntriesByIndex[i];
            CloseHandle(handles[i]);
        }
    }
    delete [] handles;
    handles = newHandles;
    handlesCount = newCount + 1;
    delete [] handlesToRemove;
    handlesToRemove = new bool[handlesCount];
    for (unsigned int i = 0; i < handlesCount; i++)
        handlesToRemove[i] = false;
    EndWrite();
}
void DirectoryCache::CleanHandles2()
{
    StartRead();
    unsigned int newCount = 0;
    for (unsigned int i = 1; i < handlesCount; i++)
    {
        if (!handlesToRemove[i])
            newCount++;
    }
    if (newCount != handlesCount - 1)
    {
        UpgradeToWriter();
        HANDLE *newHandles = new HANDLE[newCount + 1];
        newHandles[0] = handles[0];
        unsigned int j = 1;
        for (unsigned int i = 1; i < handlesCount; i++)
        {
            if (!handlesToRemove[i])
            {
                newHandles[j] = handles[i];
                cacheEntriesByIndex[j] = cacheEntriesByIndex[i];
                j++;
            }
            else
            {
                cacheEntriesByPath.erase(cacheEntriesByIndex[i]->path);
                delete cacheEntriesByIndex[i]->accessGuard;
                for (vector<ItemInfo*>::iterator k = cacheEntriesByIndex[i]->content.begin(); k != cacheEntriesByIndex[i]->content.end(); k++)
                    delete *k;
                delete cacheEntriesByIndex[i];
                CloseHandle(handles[i]);
            }
        }
        delete [] handles;
        handles = newHandles;
        handlesCount = newCount + 1;
        delete [] handlesToRemove;
        handlesToRemove = new bool[handlesCount];
        for (unsigned int i = 0; i < handlesCount; i++)
            handlesToRemove[i] = false;
        EndWrite();
    }
    else
        EndRead();
}
void DirectoryCache::MarkUnused(const wxString &path)
{
    DirectoryCacheEntry *entry;
    StartRead();
    map<wxString, DirectoryCacheEntry*>::iterator i = cacheEntriesByPath.find(path);
    if (i == cacheEntriesByPath.end())
    {
        EndRead();
        return;
    }
    else
        entry = i->second;

    wxMutexLocker locker(*entry->accessGuard);
    entry->useCount--;
    EndRead();
}
void DirectoryCache::StartWrite()
{
    RWLock_StartWrite(&readWriteLock);
}
void DirectoryCache::EndWrite()
{
    RWLock_EndWrite(&readWriteLock);
}
void DirectoryCache::StartRead()
{
    RWLock_StartRead(&readWriteLock);
}
void DirectoryCache::EndRead()
{
    RWLock_EndRead(&readWriteLock);
}
void DirectoryCache::DowngradeToReader()
{
    RWLock_DowngradeToReader(&readWriteLock);
}
void DirectoryCache::UpgradeToWriter()
{
    RWLock_UpgradeToWriter(&readWriteLock);
}
void DirectoryCache::EndAccess()
{
    RWLock_EndAccess(&readWriteLock);
}
